#include "gtest/gtest.h"
#include "servant/Application.h"
#include "TarsTest/TestcaseServer/RPCTest.h"
#include "servant/AdminF.h"
#include "TarsServantName.h"


#include "util/tc_multi_hashmap.h"
#include "util/tc_shm.h"
#include "util/tc_option.h"
#include <stdlib.h>
#include <iostream>
#include "util/tc_common.h"
// #include <sys/time.h>


using namespace tars;


////////////////////////////////////////////////////////////////////
//

namespace {
    
TC_Shm				g_shm;
TC_Multi_HashMap	g_hmap;
bool				g_bSilent = false;		// �Ƿ�Ϊ����ģʽ���������־������ȡ�ø��ߵ��ٶ�

// ��ͨ�Ĺ��ܲ���
struct FunctionTest
{
	static void initMem()
	{
#if __WORDSIZE == 64
		size_t iMemSize    = 1024*20;
#else
		size_t iMemSize    = 1024*10;
#endif
		g_shm.init(iMemSize, 0x12345);
		cout << "init mem, shm key=0x" << hex << 0x12345 << dec << endl;
	}
	
	static void initMap()
	{
		if (g_shm.iscreate())
		{
			size_t iMinSize = 2, iMaxSize = 8;
			g_hmap.initDataBlockSize(iMinSize, iMaxSize, 2.0);
			g_hmap.create(g_shm.getPointer(), g_shm.size());
			g_hmap.setEraseCount(3);
		}
		else
		{
			g_hmap.connect(g_shm.getPointer(), g_shm.size());
		}
	}
};

// �����������ܲ���
struct PerformanceTest
{
	static void initMem()
	{
		size_t iMemSize    = 1073741824;		// 1G
		g_shm.init(iMemSize, 0x23456);
		cout << "init mem, shm key=0x" << hex << 0x23456 << dec << endl;
	}
	
	static void initMap()
	{
		if (g_shm.iscreate())
		{
			size_t iMinSize = 5, iMaxSize = 20;
			g_hmap.initDataBlockSize(iMinSize, iMaxSize, 2.0);
			g_hmap.create(g_shm.getPointer(), g_shm.size());
			g_hmap.setEraseCount(10);
		}
		else
		{
			g_hmap.connect(g_shm.getPointer(), g_shm.size());
		}
	}
};

void set(const string &mk, const string &uk, const string &v, uint8_t version, bool dirty, bool full, bool head)
{
	vector<TC_Multi_HashMap::Value> vRecords;

	TC_Multi_HashMap::DATATYPE eType = (full ? TC_Multi_HashMap::FULL_DATA : TC_Multi_HashMap::PART_DATA);

	timeval tv_1, tv_2;
	TC_Common::gettimeofday(tv_1);
	
	int ret = TC_Multi_HashMap::RT_OK;
	if(uk.empty())
	{
		// ������key��only key
		ret = g_hmap.set(mk, vRecords);
	}
	else
	{
		if(v.empty())
		{
			// ����������only key
			ret = g_hmap.set(mk, uk, eType, head, vRecords);
		}
		else
		{
			ret = g_hmap.set(mk, uk, v, version, dirty, eType, head, vRecords);
		}
	}
	
	TC_Common::gettimeofday(tv_2);
	
	long lTime = 0;
	if(tv_2.tv_usec < tv_1.tv_usec)
	{
		lTime = (tv_2.tv_sec -1 - tv_1.tv_sec) * 1000000;
	}
	else
	{
		lTime = (tv_2.tv_sec - tv_1.tv_sec) * 1000000 + (tv_2.tv_usec - tv_1.tv_usec);
	}
	
	//assert(ret == 0);
	cout << "set:" << mk << "|" << uk << "|" << v << "|" << ret << "|" << lTime << endl;
	for(size_t i = 0; i < vRecords.size(); i++)
	{
		cout << "set erase:" << vRecords[i]._mkey << "|" << vRecords[i]._data._key 
			<< "|" << vRecords[i]._data._value << endl;
	}
}

void get(const string &mk, const string &uk)
{
	TC_Multi_HashMap::Value v;
	if(!uk.empty())
	{
		// ����������ѯ����
		int ret = g_hmap.get(mk, uk, v);
		cout << "get:" << mk << "|" << uk << "|" << v._data._value << "|" 
			<< (int)v._data._iVersion << "|" << v._data._dirty << "|" << ret << endl;
	}
	else
	{
		// ����key�µ���������
		size_t count = g_hmap.count(mk);
		cout << "count of " << mk << ": " << count << endl;
		TC_Multi_HashMap::lock_iterator it = g_hmap.find(mk);
		while(it != g_hmap.end())
		{
			TC_Multi_HashMap::Value v;
			int ret = it->get(v);
			cout << v._mkey << "|" << v._data._key << "|" << v._data._value 
				<< "|" << (int)v._data._iVersion << "|" << v._data._dirty << "|" << ret << endl;
			it ++;
		}
	}
}

void del(const string &mk, const string &uk)
{
	vector<TC_Multi_HashMap::Value> vtDel;
	int ret = TC_Multi_HashMap::RT_OK;
	if(uk.empty())
	{
		ret = g_hmap.del(mk, vtDel);
	}
	else
	{
		TC_Multi_HashMap::Value v;
		ret = g_hmap.del(mk, uk, v);
		vtDel.push_back(v);
	}
	cout << "del:" << mk << "|" << uk << "|" << ret << endl;
	for(size_t i = 0; i < vtDel.size(); i ++)
	{
		cout << "del:" << vtDel[i]._mkey << "|" << vtDel[i]._data._key << "|" << vtDel[i]._data._value << endl;
	}
}

void sync()
{
	TC_Multi_HashMap::Value data;
	size_t n = 0;
	g_hmap.setSyncTime(30);
	g_hmap.sync();
	while(true)
	{
		int ret = g_hmap.sync(time(NULL), data);
		if (ret == TC_Multi_HashMap::RT_OK)
		{
			break;
		}
		else if(ret == TC_Multi_HashMap::RT_NEED_SYNC)
		{
			++n;
			cout << "sync:" << data._mkey << "|" << data._data._key << "|" 
				<< data._data._value << "|" << data._data._synct << endl;
		}
	}
	cout << n << " records synced" << endl;
}

void erase(int ratio)
{
	vector<TC_Multi_HashMap::Value> vtErased;
	int ret = g_hmap.erase(ratio, vtErased, true);
	cout << "erase:" << ratio << "|" << vtErased.size() << "|" << ret << endl;
	for(size_t i = 0; i < vtErased.size(); i++)
	{
		cout << "erase:" << vtErased[i]._mkey << "|" << vtErased[i]._data._key 
			<< "|" << vtErased[i]._data._value << endl;
	}
}

void listh()
{
	const TC_Multi_HashMap::tagMapHead &head = g_hmap.getMapHead();
	cout << "list hash count: " << head._iElementCount << endl;
	TC_Multi_HashMap::hash_iterator it = g_hmap.hashBegin();
	while(it != g_hmap.hashEnd())
	{
		vector<TC_Multi_HashMap::Value> vv;
		it->get(vv);
		++it;
		
		for(size_t i = 0; i < vv.size(); i ++)
		{
			cout << vv[i]._mkey << "|" << vv[i]._data._key << "|" << vv[i]._data._value 
				<< "|" << (int)vv[i]._data._iVersion << "|" << vv[i]._data._dirty << "|" << vv[i]._data._synct << endl;
		}
	}
}

void list(int type, bool asc = true)
{
	TC_Multi_HashMap::lock_iterator it;
	switch(type)
	{
	case 0:
		// ��block������
		if(asc)
		{
			// ����
			it = g_hmap.begin();
		}
		else
		{
			// ����
			it = g_hmap.rbegin();
		}
		break;
	case 1:
		// ��getʱ��������
		if(asc)
		{
			// ����
			it = g_hmap.beginGetTime();
		}
		else
		{
			// ����
			it = g_hmap.rbeginGetTime();
		}
		break;
	case 2:
		// ��setʱ��������
		if(asc)
		{
			// ����
			it = g_hmap.beginSetTime();
		}
		else
		{
			// ����
			it = g_hmap.rbeginSetTime();
		}
		break;
	case 3:
		// ��������������
		it = g_hmap.beginDirty();
		break;
	default:
		return;
	}

	while(it != g_hmap.end())
	{
		TC_Multi_HashMap::Value v;
		it->get(v);
		++it;
		
		cout << v._mkey << "|" << v._data._key << "|" << v._data._value 
			<< "|" << (int)v._data._iVersion << "|" << v._data._dirty << "|" << v._data._synct << endl;
	}
}

void batchset(size_t iCount, size_t iAvgRec)
{
	srand(time(NULL));
	string mk, uk, v;
	vector<TC_Multi_HashMap::Value> vRecords;

	timeval tv_1, tv_2;
	TC_Common::gettimeofday(tv_1);

	for(size_t i = 1; i <= iCount / iAvgRec; i ++)
	{
		mk = TC_Common::tostr(i);
		size_t iRec = rand() % iAvgRec + 1;
		for(size_t j = 1; j <= iRec; j ++)
		{
			uk = TC_Common::tostr(j);
			v = mk + uk;

			timeval tv_1, tv_2;
			if(!g_bSilent)
			{
				TC_Common::gettimeofday(tv_1);
			}

			vRecords.clear();
			int ret = g_hmap.set(mk, uk, v, 0, true, TC_Multi_HashMap::FULL_DATA, true, vRecords);

			if(!g_bSilent)
			{
				TC_Common::gettimeofday(tv_2);
				long lTime = 0;
				if(tv_2.tv_usec < tv_1.tv_usec)
				{
					lTime = (tv_2.tv_sec -1 - tv_1.tv_sec) * 1000000;
				}
				else
				{
					lTime = (tv_2.tv_sec - tv_1.tv_sec) * 1000000 + (tv_2.tv_usec - tv_1.tv_usec);
				}
				
				//assert(ret == 0);
				cout << "set:" << mk << "|" << uk << "|" << v << "|" << ret << "|" << lTime << endl;
				for(size_t i = 0; i < vRecords.size(); i++)
				{
					cout << "set erase:" << vRecords[i]._mkey << "|" << vRecords[i]._data._key 
						<< "|" << vRecords[i]._data._value << endl;
				}
			}
		}
	}

	TC_Common::gettimeofday(tv_2);
	long lTime = 0;
	if(tv_2.tv_usec < tv_1.tv_usec)
	{
		lTime = (tv_2.tv_sec -1 - tv_1.tv_sec) * 1000000;
	}
	else
	{
		lTime = (tv_2.tv_sec - tv_1.tv_sec) * 1000000 + (tv_2.tv_usec - tv_1.tv_usec);
	}
	cout << "batchset:" << iCount << "|" << iAvgRec << "|" << lTime << endl;
}

void batchset()
{
	vector<TC_Multi_HashMap::Value> vtSet, vtErased;

	TC_Multi_HashMap::Value v;

	v._mkey = "1", v._data._key = "1", v._data._value = "11";
	vtSet.push_back(v);

	v._mkey = "1", v._data._key = "2", v._data._value = "12";
	vtSet.push_back(v);

	v._mkey = "2", v._data._key = "1", v._data._value = "21";
	vtSet.push_back(v);

	v._mkey = "3", v._data._key = "1", v._data._value = "31";
	vtSet.push_back(v);

	v._mkey = "4", v._data._key = "1", v._data._value = "41";
	vtSet.push_back(v);

	int ret = g_hmap.set(vtSet, TC_Multi_HashMap::FULL_DATA, true, true, vtErased);

	for(size_t i = 0; i < vtSet.size(); i ++)
	{
		cout << "set:" << vtSet[i]._mkey << "|" << vtSet[i]._data._key << "|" << vtSet[i]._data._value << "|" << ret << endl;
	}
	for(size_t i = 0; i < vtErased.size(); i++)
	{
		cout << "set erase:" << vtErased[i]._mkey << "|" << vtErased[i]._data._key 
			<< "|" << vtErased[i]._data._value << endl;
	}
}

void batchget(size_t iCount, size_t iAvgRec)
{
	srand(time(NULL));
	string mk, uk;
	TC_Multi_HashMap::Value v;

	timeval tv_1, tv_2;
	TC_Common::gettimeofday(tv_1);
	
	for(size_t i = 1; i <= iCount / iAvgRec; i ++)
	{
		mk = TC_Common::tostr(i);
		size_t iRec = rand() % iAvgRec + 1;
		for(size_t j = 1; j <= iRec; j ++)
		{
			uk = TC_Common::tostr(j);
			
			timeval tv_1, tv_2;
			if(!g_bSilent)
			{
				TC_Common::gettimeofday(tv_1);
			}
			
			int ret = g_hmap.get(mk, uk, v);
			
			if(!g_bSilent)
			{
				TC_Common::gettimeofday(tv_2);
				long lTime = 0;
				if(tv_2.tv_usec < tv_1.tv_usec)
				{
					lTime = (tv_2.tv_sec -1 - tv_1.tv_sec) * 1000000;
				}
				else
				{
					lTime = (tv_2.tv_sec - tv_1.tv_sec) * 1000000 + (tv_2.tv_usec - tv_1.tv_usec);
				}
				
				//assert(ret == 0);
				cout << "get:" << mk << "|" << uk << "|" << v._data._value << "|" 
					<< (int)v._data._iVersion << "|" << v._data._dirty << "|" << ret << endl;
			}
		}
	}
	
	TC_Common::gettimeofday(tv_2);
	long lTime = 0;
	if(tv_2.tv_usec < tv_1.tv_usec)
	{
		lTime = (tv_2.tv_sec -1 - tv_1.tv_sec) * 1000000;
	}
	else
	{
		lTime = (tv_2.tv_sec - tv_1.tv_sec) * 1000000 + (tv_2.tv_usec - tv_1.tv_usec);
	}
	cout << "batchset:" << iCount << "|" << iAvgRec << "|" << lTime << endl;
}


// �ڴ��С���ԣ�����һЩ�ṹ�ڲ�ͬ�Ļ�������ռ���ֽ���
void testMemSize()
{
	cout << "sizeof(int)=" << sizeof(int) << endl;
	cout << "sizeof(long)=" << sizeof(long) << endl;
	cout << "sizeof(size_t)=" << sizeof(size_t) << endl;
	cout << "sizeof(time_t)=" << sizeof(time_t) << endl;
	cout << "sizeof(tagBlockHead)=" << sizeof(TC_Multi_HashMap::Block::tagBlockHead) << endl;
	cout << "sizeof(tagMainKeyHead)=" << sizeof(TC_Multi_HashMap::MainKey::tagMainKeyHead) << endl;
	cout << "sizeof(tagChunkHead)=" << sizeof(TC_Multi_HashMap::Block::tagChunkHead) << endl;
	cout << "sizeof(tagHashItem)=" << sizeof(TC_Multi_HashMap::tagHashItem) << endl;
	cout << "sizeof(tagMainKeyHashItem)=" << sizeof(TC_Multi_HashMap::tagMainKeyHashItem) << endl;
}

void usage(char *argv)
{
    cout << argv << " --help\tshow this help message" << endl;
	cout << "\t --mem\tview memory size of some structs" << endl;
	cout << "\t --f\tdo the functionality test(default option)" << endl;
	cout << "\t --p\tdo the performance test" << endl;
	cout << "\t --s\tsilent mode in performance test, no output" << endl;
    cout << "\t --release\trelease the allocated share memory" << endl;
    cout << "\t --view\tview statistic information of this map" << endl;
    cout << "\t --set [--mkey=] [--ukey=] [--value=] [--version] [--clean] [--full] [--tail]" << endl;
    cout << "\t --get [--mkey=] [--ukey=]" << endl;
    cout << "\t --del [--mkey=] [--ukey=]" << endl;
    cout << "\t --sync" << endl;
    cout << "\t --erase [--ratio]" << endl;
    cout << "\t --isdirty [--mkey=] [--ukey=]" << endl;
    cout << "\t --listh\tlist data by hash index" << endl;
	cout << "\t --listb [--desc]\tlist data by block chain" << endl;
	cout << "\t --listg [--desc]\tlist data by get time chain" << endl;
	cout << "\t --lists [--desc]\tlist data by set time chain" << endl;
    cout << "\t --listd\tlist data by dirty data chain" << endl;
    cout << "\t --set-batch [--count] [--avgrec]\tdo batch set operation, will set total count records with avgrec records per main key" << endl;
    cout << "\t --get-batch [--count] [--avgrec]\tdo batch get operation" << endl;
	cout << "\t --chkmk [--mkey]\tcheck if main key exists" << endl;
	cout << "\t --setstate [--mkey] [--ukey] [--dirty] [--clean] [--part] [--full]\tcheck if main key exists" << endl;
	cout << "\t --chkbad [--hash] [--repair]" << endl;
}


int main_test(int argc, char* argv[])
{
	try
    {
        TC_Option option;
        option.decode(argc, argv);

        map<string, string> m = option.getMulti();

        if(option.hasParam("help") || option.getMulti().empty())
        {
            usage(argv[0]);
            return 0;
        }

		if(option.hasParam("mem"))
		{
			testMemSize();
			return 0;
		}

		g_bSilent = option.hasParam("s");

		// ������úͻ�ȡʱҲ�������ܲ���
		bool bPerfTest = option.hasParam("p");
		if(bPerfTest)
		{
			PerformanceTest::initMem();
		}
		else
		{
			FunctionTest::initMem();
		}

        if(option.hasParam("release"))
        {
            g_shm.del();
            return 0;
        }

		if(bPerfTest)
		{
			PerformanceTest::initMap();
		}
		else
		{
			FunctionTest::initMap();
		}

        if(option.hasParam("view"))
        {
            cout << g_hmap.desc() << endl;
        }
        else if(option.hasParam("set"))
        {
            string mk = option.getValue("mkey");
			string uk = option.getValue("ukey");
            string v = option.getValue("value");
			uint8_t version = option.getValue("version").empty() ? 0 : atoi(option.getValue("version").c_str());
			bool dirty = option.getValue("clean").empty() ? true : false;
			bool full = option.getValue("full").empty() ? false : true;
			bool head = option.getValue("tail").empty() ? true : false;

			if(mk.empty())
			{
				usage(argv[0]);
				return 0;
			}
			
			set(mk, uk, v, version, dirty, full, head);
        }
        else if(option.hasParam("get"))
        {
            string mk;
			string uk;
            mk = option.getValue("mkey");
			uk = option.getValue("ukey");

			if(mk.empty())
			{
				usage(argv[0]);
				return 0;
			}
			
			get(mk, uk);
        }
        else if(option.hasParam("del"))
        {
            string mk;
			string uk;
            mk = option.getValue("mkey");
			uk = option.getValue("ukey");
			
			if(mk.empty())
			{
				usage(argv[0]);
				return 0;
			}

			del(mk, uk);
        }
        else if(option.hasParam("sync"))
        {
			sync();
        }
        else if(option.hasParam("erase"))
        {
            string ratio = option.getValue("ratio");
			if(ratio.empty())
			{
				usage(argv[0]);
				return 0;
			}

			erase(TC_Common::strto<int>(ratio));
        }
        else if(option.hasParam("isdirty"))
        {
            string mk;
			string uk;
            mk = option.getValue("mkey");
			uk = option.getValue("ukey");
			if(mk.empty())
			{
				usage(argv[0]);
				return 0;
			}
            int ret = TC_Multi_HashMap::RT_OK;
			if(uk.empty())
			{
				ret = g_hmap.checkDirty(mk);
			}
			else
			{
				ret = g_hmap.checkDirty(mk, uk);
			}
            cout << "isdirty:" << mk << "|" << uk << "|" << (ret == TC_Multi_HashMap::RT_DIRTY_DATA) << endl;
        }
        else if(option.hasParam("listh"))
        {
			listh();
        }
        else if(option.hasParam("listb"))
        {
			if(option.hasParam("desc"))
			{
				list(0, false);
			}
			else
			{
				list(0, true);
			}
        }
        else if(option.hasParam("listg"))
        {
			if(option.hasParam("desc"))
			{
				list(1, false);
			}
			else
			{
				list(1, true);
			}
        }
        else if(option.hasParam("lists"))
        {
			if(option.hasParam("desc"))
			{
				list(2, false);
			}
			else
			{
				list(2, true);
			}
        }
        else if(option.hasParam("listd"))
        {
			list(3);
        }
		else if(option.hasParam("set-batch"))
		{
			string sCount = option.getValue("count");
			string sAvgRec = option.getValue("avgrec");
			if(!sCount.empty())
			{
				batchset(TC_Common::strto<size_t>(sCount), sAvgRec.empty() ? 2 : TC_Common::strto<size_t>(sAvgRec));
			}
			else
			{
				// ������������������
				batchset();
			}
		}
		else if(option.hasParam("get-batch"))
		{
			string sCount = option.getValue("count");
			string sAvgRec = option.getValue("avgrec");
			if(sCount.empty())
			{
				usage(argv[0]);
				return 0;
			}

			batchget(TC_Common::strto<size_t>(sCount), sAvgRec.empty() ? 2 : TC_Common::strto<size_t>(sAvgRec));
		}
		else if(option.hasParam("chkmk"))
		{
			string mk = option.getValue("mkey");
			if(mk.empty())
			{
				usage(argv[0]);
				return 0;
			}

			int ret = g_hmap.checkMainKey(mk);
			if(ret == TC_Multi_HashMap::RT_OK)
			{
				cout << "main key " << mk << " exists" << endl;
			}
			else if(ret == TC_Multi_HashMap::RT_ONLY_KEY)
			{
				cout << "main key " << mk << " exists, but only key" << endl;
			}
			else if(ret == TC_Multi_HashMap::RT_PART_DATA)
			{
				cout << "main key " << mk << " exists, and possible part data" << endl;
			}
			else if(ret == TC_Multi_HashMap::RT_NO_DATA)
			{
				cout << "main key " << mk << " not exists" << endl;
			}
			else
			{
				cout << "chkmk " << mk << " failed: " << ret << endl;
			}
		}
		else if(option.hasParam("setstate"))
		{
			string mk = option.getValue("mkey");
			string uk = option.getValue("ukey");
			bool bDirty = option.hasParam("dirty");
			bool bClean = option.hasParam("clean");
			bool bFull = option.hasParam("full");
			bool bPart = option.hasParam("part");
			if(mk.empty())
			{
				usage(argv[0]);
				return 0;
			}
			if(!uk.empty())
			{
				if(bDirty)
				{
					int ret = g_hmap.setDirty(mk, uk);
					cout << "setdirty:" << mk << "|" << uk << "|" << ret << endl;
				}
				else if(bClean)
				{
					int ret = g_hmap.setClean(mk, uk);
					cout << "setclean:" << mk << "|" << uk << "|" << ret << endl;
				}
				else
				{
					usage(argv[0]);
					return 0;
				}
			}
			else
			{
				if(bFull)
				{
					int ret = g_hmap.setFullData(mk, true);
					cout << "set full data:" << mk << "|" << ret << endl;
				}
				else if(bPart)
				{
					int ret = g_hmap.setFullData(mk, false);
					cout << "set part data:" << mk << "|" << ret << endl;
				}
				else
				{
					usage(argv[0]);
					return 0;
				}
			}
		}
		else if(option.hasParam("chkbad"))
		{
			string sHash = option.getValue("hash");
			if(sHash.empty())
			{
				usage(argv[0]);
				return 0;
			}
			uint32_t iHash = TC_Common::strto<uint32_t>(sHash);
			size_t iCount = g_hmap.checkBadBlock(iHash, option.hasParam("repair"));
			cout << iCount << " bad block under hash " << iHash << endl;
		}
		else
		{
			usage(argv[0]);
		}
	}
	catch(exception &e)
	{
		cout << "error! " << e.what() << endl;
	}

	return 0;
}



TEST(TarsUtilTestcase, UT_TC_Multi_HashMap)
{
    char * g_array_argv[6][3] = { 

    };
    
    for(unsigned int i=0; i<(sizeof(g_array_argv)/sizeof(char*[3])); i++)
    {
        char **argv = g_array_argv[i];
        int argc = (sizeof(g_array_argv[i]) / sizeof(char *));
        main_test(argc, argv);
    }        
}


}